Problem statement: In this project, we tried to make classifications of the trails based on their length and change in elevation. We also made one function that would simplify the process of classification. To generate this analysis, we used boundary, buildings, contours_3m, and trails layers from macleish package and contours250k layer from MassGIS.

#devtools::install_github("beanumber/macleish")
library(tidyverse)
library(sf)
library(macleish)
library(leaflet)
library(dplyr)
library(lwgeom)
library(ggplot2)
#read contours250k file
elevation <- st_read("contours250k/CONTOURS250K_ARC.shp")
#intersect the elevation data and the macleish_layers[["boundary"]] to get the elevation data for the macleish area
elevation <- st_transform(elevation, 4326)
elevInter <- 
  elevation %>%
  st_intersection(macleish_layers[["boundary"]])
#plot the elevation layer and the macleish trails layer together in the ggplot
ggplot() + 
  geom_sf(data = elevInter, aes(color = factor(CONTOUR_FT), colors = "Set1")) +
  geom_sf(data = macleish_layers[["trails"]], aes(color = factor(name), alpha = 0.8, lwd = 1.2)) +
  theme(legend.text=element_text(size=15))

To rate the difficulty of the trail, we wanted to check the change of elevation. We made a contour map of the MacLeish Field Station, using different colors to represent different contours levels and different trails. In this graph, each two consecutive contour lines represent a 30 ft change in elevation. To see the trails more clearly, we changed the alpha and the size so that pathways can stand out from the contour lines. We created a leaflet map to make an interactive visualization of elevation and trails as follows.

#plot the elevation data and the trails in an open street map in leaflet
leaflet() %>%
  addTiles() %>%
  addPolylines(data = st_geometry(elevInter), weight = 2) %>%
  addPolylines(data = st_geometry(macleish_layers[["trails"]]), color = macleish_layers[["trails"]]$color) %>%
  addProviderTiles(providers$OpenStreetMap)
#change the color name in macleish trails
newTrails <- macleish_layers[["trails"]] %>%
  mutate(colorName = tolower(color))

#calculate each part of the length of the trail
length <- 
  macleish_layers[["trails"]] %>%
  group_by(name) %>%
  st_length()

#make a new column in newTrails data frame
newTrails <- 
  newTrails %>%
  mutate(length = 1)

#change the value in the length column to the corresponding value in length set
for (k in 1:nrow(newTrails)){
  newTrails$length[k] <- length[k]
}

#get the total length of each trail
newTrails <- 
  newTrails %>%
  group_by(name) %>% 
  summarise(length = sum(length)) %>%
  arrange(desc(length))
#intersect elevation with trails to get the change of trails in the contour line
trailInter <- 
  elevation %>%
  st_intersection(macleish_layers[["trails"]])
#the trails with NA has a change in elevation of 0
trailInter <- 
  trailInter %>%
  group_by(name) %>%
  summarise(ele = ifelse(n_distinct(CONTOUR_FT) != 1, sd(CONTOUR_FT), 0)) %>%
  arrange(desc(ele))

We wanted to look at the exact total length and the change in elevation to rate more accurately. Therefore, we generated the table newTrails to calculate the total length of each path and the table trailInter to calculate the change in elevation. For the elevation change, we computed the standard deviation of the contour value based on the intersections between the elevation data and the macleish trail data for each trail. Higher standard deviation represents larger changes in elevation and a higher level of difficulty. If a trail doesn’t show up in the trailInter table, which means there is no intersection between the contour lines and that trail, we will regard it as that trail barely changes elevation.

#build the joined data frame with the level
joinTrail <- 
  macleish_layers[["trails"]] %>%
  st_join(trailInter, join = st_crosses)

newTrailsNone <- 
  st_set_geometry(newTrails, NULL)
trailInterNone <- 
  st_set_geometry(trailInter, NULL)

joined <- 
  newTrailsNone %>%
  left_join(trailInterNone, by = "name")

joined$ele[is.na(joined$ele)] <- 0

joined <- 
  joined %>%
  mutate(difficulty = 0.4 * as.numeric(length) + 0.6 * as.numeric(ele)) %>%
  arrange(desc(difficulty))

joined$ID <- seq.int(nrow(joined))

joined <- 
  joined %>%
  mutate(level = ifelse(ID < nrow(joined)/3 + 1, "hard", ifelse(ID > 2*nrow(joined)/3, "easy", "moderate")))
joined
#add the different levels to the macleish trails
macleish_layers[["trails"]] <- 
  macleish_layers[["trails"]] %>%
  inner_join(joined, by = "name")

We then joined newTrails and trailInter together, calculating the difficulty following the formula \(diffculty = 0.4 \cdot length+ 0.6\cdot ele\) Considering riding uphills is more difficult than riding flat, we assigned the level of significance to be 0.4 for length and 0.6 for the standard deviation of elevation. We ranked the difficulty, and assigned the top 1/3 in the ranking with “high” level, which are Snowmobile Trail, Eastern Loop, and Western Loop, the middle 1/3 as “moderate”, which are Poplar Hill Road, Porcupine Trail, and Vernal Pool Loop, and the bottom 1/3 as “easy”, which are entry trail, Driveway, Easy Out.

#set color palette
pall <- colorNumeric(palette="viridis",
                     domain= elevInter$CONTOUR_FT)
palTrail <- colorFactor(c("red", "blue", "black"), domain = macleish_layers[["trails"]]$level)
#plot contour line and trails with leaflet
leaflet(data=macleish_layers[["trails"]]) %>%
  addTiles() %>%
  addPolylines(data=elevInter, 
               color = ~pall(CONTOUR_FT), 
               group = "Contours Lines", weight = 1) %>%
  addLegend("bottomleft", pal = pall, 
            values = ~elevInter$CONTOUR_FT, 
            title = "Macleish Contour Line in feet") %>%
  addPolylines(data = macleish_layers[["trails"]], weight = 2, label = ~name, color = ~palTrail(level), group = "Trails") %>%
  addLegend("bottomright", pal = palTrail, 
            values = ~macleish_layers[["trails"]]$level, 
            title = "Difficulty levels for biking") %>%
  addMeasure() %>%
  addControl("Macleish Trails Ranked by Difficulty Levels", position = "topleft") %>%
  addMiniMap(
    toggleDisplay = TRUE) %>%
  addProviderTiles(providers$OpenStreetMap) %>%
  addProviderTiles("Esri.WorldTopoMap", group = "Topo") %>% 
  #and a satellite imagery map layer
  addProviderTiles("Esri.WorldImagery", group = "Sat") %>%
  #add the control to switch between the two
  addLayersControl(baseGroups = c("OpenSreetMap", "Topo","Sat"),
                   options = layersControlOptions(collapsed = F)) %>%
  addEasyButton(easyButton(
    icon="fa-crosshairs", title="Locate Me",
    onClick=JS("function(btn, map){ map.locate({setView: true}); }")))
#set color palette for contour lines
pall2 <- colorNumeric(palette="viridis",
                     domain= macleish_layers[["contours_3m"]]$ELEV_M)
#set color palette for different types of trails
palTrail <- colorFactor(c("red", "blue", "black"), domain = macleish_layers[["trails"]]$level)
#plot the graph with trails and contours line with leaflet
leaflet(data=macleish_layers[["trails"]]) %>%
  addTiles() %>%
  addPolylines(data=macleish_layers[["contours_3m"]], 
               color = ~pall2(ELEV_M), 
               group = "Contours Lines", weight = 1) %>%
  addLegend("bottomleft", pal = pall2, 
            values = ~macleish_layers[["contours_3m"]]$ELEV_M, 
            title = "Macleish Contour Line in meters") %>%
  addPolylines(data = macleish_layers[["trails"]], weight = 2, label = ~name, color = ~palTrail(level), group = "Trails") %>%
  addLegend("bottomright", pal = palTrail, 
            values = ~macleish_layers[["trails"]]$level, 
            title = "Difficulty levels for biking") %>%
  addMeasure() %>%
  addControl("Macleish Trails Ranked by Difficulty Levels", position = "topleft") %>%
  addMiniMap(
    toggleDisplay = TRUE) %>%
  addProviderTiles(providers$OpenStreetMap) %>%
  addProviderTiles("Esri.WorldTopoMap", group = "Topo") %>% 
  #a satellite imagery map layer
  addProviderTiles("Esri.WorldImagery", group = "Sat") %>%
  #add the control to switch between the two
  addLayersControl(baseGroups = c("OpenSreetMap", "Topo","Sat"),
                   options = layersControlOptions(collapsed = F)) %>%
  addEasyButton(easyButton(
    icon="fa-crosshairs", title="Locate Me",
    onClick=JS("function(btn, map){ map.locate({setView: true}); }")))

To visualize the trails regarding their difficulty on an interactive map, we assigned colors respectively to level “hard,” “easy,” “moderate” to red, blue, black. We added labels instead of popups to each trail so that the name of a trail would display when the mouse is placed on it and doesn’t block the map. We also assigned color palette to contour lines to visualize the change in elevation: the lowest would be in yellow and the highest would be in purple. We made two maps using data from contours250k and macleish layer contours_3m respectively. In the map, the minimap can be minimized, and a locate_me button to show the position of the current viewer so that the viewer can see the distance between his or her current location and the macleish center.

Smith can use our classification in forming orientation group. Riding is one of the most popular outdoor orientation groups. Participants bike down the trails and explore Mount Tom State Reservation. However, people with different backgrounds may feel about the routes differently. Some would consider the trail easy; others might be exhausted after the trip. So I would suggest that Smith offers two groups for Riding: Ride I and Ride II. Ride I explores the easy/moderate level trails for people don’t have much experience biking, while Ride II goes down the moderate/hard level trails for experienced bikers who are confident with riding uphills so that people can choose the group that fits them well and enjoy the Macleish field station.

The distance between Smith College and Macleish field station is roughly 17.4 km, while the average length of the trails is 2km. With an average speed of 15.5 km/h, although speed may vary according to the difficulty levels of the trails, we can assume that each route takes no more than 2.5 hours, which is an appropriate duration for a one-day bike trip. The implementation of the bike trail would also reduce the carbon footprint of the students since they are biking instead of taking the bus or driving so that it would have a positive influence on the environment. However, it is not possible for everyone to bike non-stop for 2 hours, so I encourage Smith to install water fountain and little gazebos alone the way so that the bikers can take the rest they need and continue the journey energetically.


  1. Github link: https://github.com/ChristinaLyu/miniProject3.git